import asyncio
import os
import logging
import hashlib
import json
from datetime import datetime
from typing import List, Dict, Optional, Tuple
import edge_tts
import io

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class EdgeTTSService:
    def __init__(self, voice: str = "zh-CN-XiaoxiaoNeural", output_dir: str = "web_output"):
        """
        初始化 Edge TTS 服务
        
        Args:
            voice: 要使用的声音名称，默认使用中文女声
            output_dir: 音频文件输出目录
        """
        self.voice = voice
        self.output_dir = output_dir
        self.cache_index_file = os.path.join(output_dir, "cache_index.json")
        self._ensure_output_dir()
        self._load_cache_index()
        logger.info(f"初始化 TTS 服务: voice={voice}, output_dir={output_dir}")

    def _ensure_output_dir(self):
        """确保输出目录存在"""
        os.makedirs(self.output_dir, exist_ok=True)
        
    def _get_date_dir(self) -> str:
        """获取当前日期的目录路径"""
        current_date = datetime.now().strftime("%Y-%m-%d")
        date_dir = os.path.join(self.output_dir, current_date)
        os.makedirs(date_dir, exist_ok=True)
        return date_dir

    def _generate_hash(self, text: str, voice: str) -> str:
        """
        根据文本和语音选项生成哈希值
        """
        content = f"{text}_{voice}".encode('utf-8')
        return hashlib.md5(content).hexdigest()

    def _load_cache_index(self):
        """加载缓存索引"""
        try:
            if os.path.exists(self.cache_index_file):
                with open(self.cache_index_file, 'r', encoding='utf-8') as f:
                    self.cache_index = json.load(f)
            else:
                self.cache_index = {}
        except Exception as e:
            logger.error(f"加载缓存索引失败: {str(e)}")
            self.cache_index = {}

    def _save_cache_index(self):
        """保存缓存索引"""
        try:
            with open(self.cache_index_file, 'w', encoding='utf-8') as f:
                json.dump(self.cache_index, f, ensure_ascii=False, indent=2)
        except Exception as e:
            logger.error(f"保存缓存索引失败: {str(e)}")

    def get_cache_path(self, text: str, voice: str) -> Tuple[str, bool]:
        """
        获取缓存文件路径，并检查是否存在
        
        Args:
            text: 要转换的文本
            voice: 语音选项
            
        Returns:
            (文件路径, 是否存在) 的元组
        """
        # 生成哈希值
        file_hash = self._generate_hash(text, voice)
        
        # 检查缓存索引
        if file_hash in self.cache_index:
            cache_info = self.cache_index[file_hash]
            file_path = cache_info['path']
            if os.path.exists(file_path):
                logger.info(f"找到缓存文件: {file_path}")
                return file_path, True
            else:
                # 如果文件不存在，从索引中删除
                del self.cache_index[file_hash]
                self._save_cache_index()
        
        # 如果没有缓存或缓存无效，创建新文件路径
        date_dir = self._get_date_dir()
        timestamp = datetime.now().strftime("%H%M%S")
        filename = f"{file_hash}_{timestamp}.mp3"
        file_path = os.path.join(date_dir, filename)
        
        return file_path, False

    def _update_cache_index(self, text: str, voice: str, file_path: str):
        """更新缓存索引"""
        file_hash = self._generate_hash(text, voice)
        self.cache_index[file_hash] = {
            'path': file_path,
            'text': text,
            'voice': voice,
            'created_at': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        }
        self._save_cache_index()

    def clean_old_files(self, days_to_keep: int = 7):
        """
        清理指定天数之前的缓存文件
        
        Args:
            days_to_keep: 保留最近几天的文件
        """
        try:
            import shutil
            from datetime import timedelta
            
            # 获取当前日期
            current_date = datetime.now()
            cutoff_date = current_date - timedelta(days=days_to_keep)
            
            # 更新缓存索引
            updated_cache = {}
            for file_hash, cache_info in self.cache_index.items():
                try:
                    file_date = datetime.strptime(cache_info['created_at'].split()[0], "%Y-%m-%d")
                    if file_date >= cutoff_date and os.path.exists(cache_info['path']):
                        updated_cache[file_hash] = cache_info
                except (ValueError, KeyError):
                    continue
            
            # 保存更新后的缓存索引
            self.cache_index = updated_cache
            self._save_cache_index()
            
            # 清理旧文件夹
            for date_dir in os.listdir(self.output_dir):
                if date_dir == "cache_index.json":
                    continue
                try:
                    dir_date = datetime.strptime(date_dir, "%Y-%m-%d")
                    if dir_date < cutoff_date:
                        dir_path = os.path.join(self.output_dir, date_dir)
                        shutil.rmtree(dir_path)
                        logger.info(f"已清理过期目录: {dir_path}")
                except ValueError:
                    continue
                
        except Exception as e:
            logger.error(f"清理过期文件失败: {str(e)}")

    async def text_to_speech(self, text: str) -> str:
        """
        将文本转换为语音，优先使用缓存
        
        Args:
            text: 要转换的文本
            
        Returns:
            音频文件路径
        """
        try:
            if not text:
                raise ValueError("文本不能为空")

            # 获取缓存文件路径
            file_path, exists = self.get_cache_path(text, self.voice)
            
            # 如果缓存存在，直接返回
            if exists:
                return file_path
            
            logger.info(f"开始生成语音: text={text[:50]}...")
            
            # 检查声音是否存在
            if "zh-CN" in self.voice:
                voices = await self.get_chinese_voices()
            elif "en-" in self.voice:
                voices = await self.get_english_voices()
            else:
                voices = await self.list_available_voices()
                
            if not any(voice["ShortName"] == self.voice for voice in voices):
                raise ValueError(f"无效的声音选项: {self.voice}")

            # 生成新的语音文件
            communicate = edge_tts.Communicate(text, self.voice)
            await communicate.save(file_path)
            
            if not os.path.exists(file_path):
                raise RuntimeError("语音文件生成失败")
            
            # 更新缓存索引
            self._update_cache_index(text, self.voice, file_path)
                
            logger.info(f"语音生成成功，已保存到: {file_path}")
            return file_path
            
        except Exception as e:
            logger.error(f"语音生成失败: {str(e)}")
            raise

    @staticmethod
    async def list_available_voices() -> List[Dict]:
        """
        获取所有可用的声音列表
        
        Returns:
            声音信息列表
        """
        try:
            voices = await edge_tts.list_voices()
            logger.info(f"获取到 {len(voices)} 个可用声音")
            return voices
        except Exception as e:
            logger.error(f"获取声音列表失败: {str(e)}")
            raise

    @staticmethod
    async def get_chinese_voices() -> List[Dict]:
        """
        获取所有中文声音列表
        
        Returns:
            中文声音信息列表
        """
        try:
            voices = await edge_tts.list_voices()
            chinese_voices = [voice for voice in voices if "zh-CN" in voice["ShortName"]]
            logger.info(f"获取到 {len(chinese_voices)} 个中文声音")
            return chinese_voices
        except Exception as e:
            logger.error(f"获取中文声音列表失败: {str(e)}")
            raise

    @staticmethod
    async def get_english_voices() -> List[Dict]:
        """
        获取所有英文声音列表
        
        Returns:
            英文声音信息列表
        """
        try:
            voices = await edge_tts.list_voices()
            english_voices = [voice for voice in voices if "en-" in voice["ShortName"]]
            logger.info(f"获取到 {len(english_voices)} 个英文声音")
            return english_voices
        except Exception as e:
            logger.error(f"获取英文声音列表失败: {str(e)}")
            raise

async def main():
    # 使用示例
    service = EdgeTTSService()
    
    # 列出所有中文声音
    chinese_voices = await service.get_chinese_voices()
    print("可用的中文声音：")
    for voice in chinese_voices:
        print(f"名称: {voice['ShortName']}")
        print(f"性别: {voice['Gender']}")
        print(f"语言: {voice['Locale']}")
        print("-" * 30)
    
    # 生成语音示例
    text = "这是一个使用 Edge TTS 服务的测试。"
    audio_path = await service.text_to_speech(text)
    print(f"已生成音频文件: {audio_path}")

if __name__ == "__main__":
    asyncio.run(main()) 